package com.lucan.tracking.compiler;

import com.google.auto.service.AutoService;
import com.lucan.tracking.annotations.Bean;
import com.lucan.tracking.annotations.Param;
import com.squareup.javapoet.*;

import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.lucan.tracking.utils.Consts.BEANS_FILE_NAME;
import static com.lucan.tracking.utils.Consts.PACKAGE_OF_CONFIG_FILE;

/**
 * @author qihailong
 * @date 2018/7/10 下午5:08
 * @description BeansProcessor
 */

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BeansProcessor extends BaseProcessor {

    private Map<String, String> mUrlMap = new HashMap<>();
    private Map<String, Map<String, String>> mParamsMap = new HashMap<>();

    @Override
    public Class[] getSupportAnnotations() {
        return new Class[]{Bean.class};
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        if (set != null && set.size() != 0) {
            Set<? extends Element> elements =
                    roundEnv.getElementsAnnotatedWith(Bean.class);
            try {
                getLogger().info("Found @Bean, start...");
                parseBeans(elements);
                buildBeansFile();
                getLogger().info("Parse @Bean complete");
            } catch (Exception e) {
                getLogger().error(e);
            }
            return true;
        }
        return false;
    }

    private void parseBeans(Set<? extends Element> elements) throws ClassNotFoundException, IOException {
        if (elements == null || elements.size() == 0) {
            getLogger().error("no @Bean are found");
            return;
        }
        for (Element element : elements) {
            TypeElement typeElement = (TypeElement) element;
            Bean bean = typeElement.getAnnotation(Bean.class);
            String beanId = bean.id();
            String baseUrl = bean.api();
            mUrlMap.put(beanId, baseUrl);
            mParamsMap.put(beanId, getParams(typeElement));
            getLogger().info("BeanId: " + beanId + ", baseUrl: " + baseUrl);
        }

    }

    private void buildBeansFile() throws IOException {
        TypeSpec.Builder builder = TypeSpec.classBuilder(BEANS_FILE_NAME)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
        for (String key : mUrlMap.keySet()) {
//            builder.addField(buildStaticConfigField(key, String.class,
//                    "$S", mBeanMap.get(key)));
            MethodSpec getUrlMethod = MethodSpec.methodBuilder("get" + key + "Url")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
                    .returns(TypeName.get(String.class))
                    .addCode(CodeBlock.builder().add("return \"" + mUrlMap.get(key) + "\";\n").build())
                    .build();

            MethodSpec getParamsMethod = MethodSpec.methodBuilder("get" + key + "Params")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
                    .returns(TypeName.get(Map.class))
                    .addStatement("Map<String,String> map = new $T<>()", HashMap.class)
                    .addCode(CodeBlock.builder().add(buildCode(mParamsMap.get(key))).build())
                    .addStatement("return map")
                    .build();
            builder.addMethod(getUrlMethod);
            builder.addMethod(getParamsMethod);

        }
        JavaFile.builder(
                PACKAGE_OF_CONFIG_FILE,
                builder.build()
        ).build().writeTo(getFiler());
    }

    private String buildCode(Map<String, String> params) {
        StringBuilder code = new StringBuilder();
        if (params != null) {
            for (String key : params.keySet()) {
                code.append("map.put(");
                code.append("\"" + key + "\"");
                code.append(",");
                code.append("\"" + params.get(key) + "\"");
                code.append(");\n");
            }
        }
        return code.toString();
    }

    /**
     * @param typeElement
     * @return
     */
    private Map<String, String> getParams(TypeElement typeElement) {
        Map<String, String> params = new HashMap<>();
        for (Element element : typeElement.getEnclosedElements()) {
            if (element.getKind().isField()) {
                Param param = element.getAnnotation(Param.class);
                if (param != null) {
                    params.put(param.name(), param.value());
                }
            }
        }
        return params;
    }
}
